package agendaSystem;

import java.util.*;

import be.kuleuven.cs.som.annotate.*;
import agendaSystem.Entry;

/**
 * 
 * A class for the creation of a calendar, consisted of several entries and owned by one person.
 *
 */
public class Agenda {
		
	/**
	 * Initialize a new agenda with a given owner and given entries.
	 * 
	 * @param	owner
	 * 			The given owner for this new agenda
	 * @param	entries
	 * 			The given entries for this new agenda
	 * @pre		The given entries may not overlap with each other.
	 * 			| !overlapsWith(other)
	 * @post	The agenda has one owner
	 * 			| new.getOwner() == owner 
	 * @post	The agenda consists of the given entries
	 * 			| new.getAllEntries() == entries
	 */
	public Agenda(String owner, Entry... entries) {
			this.owner = owner;
			assert entries != null; 
			assert entries.length != 0;
			for (Entry entry : entries)
				addEntry(entry);
	}
	
	/**
	 * Initialize a new agenda with a given owner.
	 * 
	 * @param	owner
	 * 			The given owner for this new agenda
	 * @post	The agenda has one owner
	 * 			| new.getOwner() == owner 
	 */
	public Agenda(String owner) {
			this.owner = owner;
	}
	
	/**
	 * @return	Returns the owner of the agenda.		
	 */
	@Basic
	public String getOwner() {
		return this.owner; 
	}

	/**
	 * Terminate this agenda.
	 * 
	 * @post	This agenda is terminated.
	 * 			| new.isTerminated()
	 * @effect	Each entry associated with this agenda is removed as entry
	 * 			| for each entry in getAllEntries():
	 * 			| 	removeEntry(entry)	
	 */
	public void terminate() {
		if(!isTerminated()) {
			List<Entry> newList = this.getAllEntries();
			for (Entry en : newList)
				removeEntry(en);
			this.isTerminated = true;
		}	
	}
	
	/**
	 * Return a boolean indicating whether or not this agenda
	 * is terminated.
	 */
	@Basic @Raw
	public boolean isTerminated() {
		return this.isTerminated;
	}
	
	/**
     * Add the given entry to the list of entries registered in
     * this agenda.
     * 
     * @param   entry
     *          The entry to be added.
     * @pre     The given entry is effective.
     *        	| entry != null
     * @pre		The agenda must be a valid agenda for this entry.
     * 			| entry.canHaveAsAgenda(this)
     * @post    The given entry is registered as one of the
     *          entries in a list of entries for this agenda.
     *        	| new.hasAsEntry(entry)
     * @post	If the map doesn't contain a list for the day of the entry, then we add the entry and
     * 			we add the agenda to the list of agendas for the given entry.
     * 			| if (this.entriesInAgenda.get(entry.getDay()) == null)
     * 			| 	then temporaryList.add(entry)
     * 			|		this.entriesInAgenda.put(entry.getDay(), temporaryList)
     * 			|		entry.getAgenda.add(this)
     * @post	If the map contains a list for the day of the entry, then we create a list containing the
     * 			current entries and we check them for overlap. If they don't overlap we add the given entry
     * 			and we add the agenda to the list of agendas for the given entry.
     * 			| if (this.entriesInAgenda.get(entry.getDay()) != null)
     * 			| for each entry in temporaryList check overlapsWith()
     * 			| if (overlapsWith == false)
     * 			| 	then temporaryList.add(entry)
     * 			|		this.entriesInAgenda.put(entry.getDay(), temporaryList)
     * 			|		entry.getAgenda.add(this)
     * @throws	IllegalArgumentException
     * 			The entry must not overlap with other entries of the agenda.
     * 			| for each entry in temporaryList
     * 			|	!overlapsWith()
     */
	public void addEntry(Entry entry) throws IllegalArgumentException {
		assert entry != null;
		assert entry.canHaveAsAgenda(this);
		List<Entry> temporaryList = new ArrayList<Entry>();
		Integer k = entry.getDay();
		if ((this.entriesInAgenda.get(k)) == null) {
			temporaryList.add(entry);
			this.entriesInAgenda.put(k, temporaryList);
			entry.getAgenda().add(this);
		} else {
			temporaryList = this.entriesInAgenda.get(k);
			for (Entry beginEntry : temporaryList) {
				if (beginEntry.overlapsWith(entry)) {
					throw new IllegalArgumentException(
							"Entry overlaps with the given entry");
				}
			}
			temporaryList.add(entry);
			this.entriesInAgenda.put(k, temporaryList);
			entry.getAgenda().add(this);
		}	
	}

	/**
     * Remove the given entry from the list of entries registered in
     * this agenda.
     * 
     * @param  	entry
     *         	The entry to be removed.
     * @pre    	The given entry is registered as one of the entries in a list of entries
     *        	for this agenda.
     *        	| entry.getAgenda().contains(this)
     * @pre		Entry is not terminated
     * 			| ! entry.isTerminated
     * @post    This agenda no longer has the given entry as one
     *          of its entries in a list of entries.
     *        	| ! new.entry.getAgenda.contains(this)
     * @post 	If the entry is no longer present in any agenda, the entry is terminated.
     * 		  	| if(this.getAgenda().isEmpty())
     *		  	|		then this.terminate()	
     */
	public void removeEntry(Entry entry) {
		assert ! entry.isTerminated;
		List<Agenda> agendasOfEntries= new ArrayList<Agenda> ();
		agendasOfEntries= entry.getAgenda();
		assert agendasOfEntries.contains(this);	
		Integer k = entry.getDay();
		List<Entry> huidigeLijst = this.entriesInAgenda.get(k);
		huidigeLijst.remove(entry);	
		this.entriesInAgenda.put(k, huidigeLijst);
		entry.removeAgenda(this); 
		if (entry.getAgenda().isEmpty()) {
			entry.terminate();
		}
	}

	/**
	 * Returns the list of entries at the given day, if any, registered in this agenda.
	 * 
	 * @param	day
	 * 			The day of the entries to be returned.
	 * @return	Returns a list containing all entries for the given day, if the agenda contains entries on the given day.
	 * 			| if (! this.entriesInAgenda.get(day).isEmpty())
	 * 			| 		then return this.entriesInAgenda.get(day)
     * @throws	IndexOutOfBoundsException
	 * 			The given day must be a valid day for this agenda. The day must be present in the agenda.
	 * 			| (this.entriesInAgenda.containsKey(day))
     */
	@Basic @Raw
	public List<Entry> getEntriesAt(int day) throws IndexOutOfBoundsException {
		Integer d = day;
		if(! this.entriesInAgenda.containsKey(d)){
			throw new IndexOutOfBoundsException("This is not a valid day.");
		}
		if(this.entriesInAgenda.get(d).isEmpty())
			return null;
		else
			return this.entriesInAgenda.get(d);
			
	}
	
	/**
	 * Returns a list of all entries associated with this agenda.
	 * 
	 * @post	The number of elements in the resulting list is equal to the number
	 * 			of entries in the agenda.
	 * 			| result.size() == getAllEntries() 	
	 * @return	For each day in the agenda, we get the list with entries. We add these entries to a result list. 
	 * 			The result list contains all the entries of all days in this agenda.
	 * 			| For each day in entriesInAgenda
	 * 			|		for each entry in List<Entry> entriesOfAgenda
	 * 			|			result.add(entry)
	 * 			|				return result 
	 */
	public List<Entry> getAllEntries() {
		Set<Integer> daysInMap = this.entriesInAgenda.keySet();
		List<Entry> result = new ArrayList<Entry>();
		for (Integer dayInMap : daysInMap) {
			List<Entry> resultTijdelijk = new ArrayList<Entry>();
			resultTijdelijk = this.getEntriesAt(dayInMap);
			if(resultTijdelijk != null)
			result.addAll(resultTijdelijk);
		}
		return result;
	}
		
	/** Checks whether a list of slots is available for a given agenda.
	 * 
	 * @param	givenslots
	 * 			A list of slots for which you want to check if they are available or not.
	 * @param	day
	 * 			The day for which you want to check if the given slots are available or not.
	 * @return	True if and only if all the givenslots from all the entries on that day for this agenda
	 * 			are set on false.
	 *      	| For each entry in entryList on day day
	 *      	| 		slotsOfEntry = entry.getSlots()
	 *      	| 			For each slot in slotsOfEntry
	 *      	| 				if slotsOfEntry[givenslot] == true
	 *      	|					return false
	 * @throws	ArrayIndexOutOfBoundsException
	 * 			Each slot in the given array of slots must be valid.
	 * 			| (isValidSlot(givenslot))
	 *
	 */
	public boolean hasSlotsAvailable(int day, int... givenslots) {
		Integer d = day;
		List<Entry> entryList = new ArrayList<Entry>();
		entryList = this.getEntriesAt(d);
		try {
			for (Entry entry : entryList) {
				for (int slot : givenslots) {
					boolean[] slotsOfEntry = entry.getSlots();
					if (slotsOfEntry[slot - 1] == true)
						return false;
				}			
			}
		} catch (ArrayIndexOutOfBoundsException e) {
			System.err.println("This is not a valid slot. " + e.getMessage());
			e.printStackTrace();
		}
		return true;
	}
	
	/**
     * Variable referencing a map collecting all the entries
     * registered in this agenda.
     * 
     * @invar   The referenced map is effective.
     *        | entriesInAgenda != null
     * @invar   Each key registered in the map is an effective
     *          integer.
     *        | for each key in entriesInAgenda.keySet():
     *        |   (key != null)
     * @invar   Each value associated with a key in the map is
     *          an effective, non-terminated list of entries involving this
     *          agenda whose day is identical
     *          to the associating key.
     *        | for each key in entriesInAgenda.keySet():
     *        |   (entriesInAgenda.get(key) != null) &&
     *        |   (! entriesInAgenda.get(key).isTerminated()) &&
     *        |   (entriesInAgenda.get(key).getAgenda().contains(this))
     */
	private final Map<Integer,List<Entry>> entriesInAgenda = new HashMap<Integer,List<Entry>>();
	
	/**
	 * Variable registering the owner of an agenda
	 */
	private String owner;
	
	/**
	 * Variable registering whether this agenda is terminated.
	 */
	private boolean isTerminated = false;
}
	
